import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseListener;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;

/**
*	A class to represent the minesweeper game.
*	It deals with the logic, as well as display and input.
**/
class Game extends Canvas implements MouseListener, //for clicking,
							MouseMotionListener,//for dragging
							KeyListener,//for using the 'shift' key on one-button mice.
							Runnable//used for the timer.
{

	/**
	*	Is there a game currently in progress (or, has the game started..?)
	*	Used with the timer.
	**/
	public boolean gameRunning;

	/**
	*	Double buffering
	**/
	private Graphics graphicsBuffer;

	/**
	*	Double buffering
	**/
	private Image imageBuffer;

	/**
	*	The LED showing how many mines are left
	**/
	private LEDPanel minesLeftLED;

	/**
	*	The LED showing the time elapsed
	**/
	private LEDPanel timerLED;

	private int numberCleared;


	public static final int GAME_DIFFICULTY_BEGINNER = 0;
	public static final int GAME_DIFFICULTY_INTERMEDIATE = 1;
	public static final int GAME_DIFFICULTY_EXPERT = 2;
	public static final int GAME_DIFFICULTY_CUSTOM = 3;

	public  static final Color OUTER_SHADING = Color.WHITE;

	public static final Color INNER_SHADING = Color.GRAY;

	public static final Color SURFACE_SHADING = Color.GRAY.brighter();

	public static final int PIXELS_OUTER_NW_SLOPE = 3;
	public static final int PIXELS_NW_SURFACE = 6;

	public static final int PIXELS_SE_SURFACE = 5;

	public static final int PIXELS_INNER_UPPER_SLOPE = 2;
	public static final int PIXELS_INNER_FIELD_SLOPE = 3;

	public static final int PIXELS_INNER_UPPER_SURFACE_HEIGHT = 33;

	public static final int PIXELS_FIELD_TOPMOST  = 55;
	public static final int PIXELS_FIELD_LEFTMOST = 12;

	public static final int PIXELS_UPPER_ABOVE_LED = 4;
	public static final int PIXELS_UPPER_BESIDE_LED = 5;


	/**
	*	The number of cells wide at easy setting
	**/
	public static final int EASY_WIDTH = 9;
	
	/**
	*	The number of cells heigh at easy setting.
	**/
	public static final int EASY_HEIGHT = 9;

	/**
	*	The number of mines at easy setting.
	**/
	public static final int EASY_NUM_MINES = 10;


	

	/**
	*	The number of cells wide at intermediate setting
	**/
	public static final int INTERMEDIATE_WIDTH = 16;
	
	/**
	*	The number of cells heigh at intermediate setting.
	**/
	public static final int INTERMEDIATE_HEIGHT = 16;

	/**
	*	The number of mines at intermediate setting.
	**/
	public static final int INTERMEDIATE_NUM_MINES = 40;


	/**
	*	The maximum number of cells (width) of the mine field.
	**/

	public static final int MAX_WIDTH = 30;

	/**
	*	The maximum number of cells (height) of the mine field.
	**/

	public static final int MAX_HEIGHT = 30;



	
	/**
	*	The number of cells wide at expert setting
	**/
	public static final int EXPERT_WIDTH = 30;
	
	/**
	*	The number of cells heigh at expert setting.
	**/
	public static final int EXPERT_HEIGHT = 16;

	/**
	*	The number of mines at expert setting.
	**/
	public static final int EXPERT_NUM_MINES = 99;


	/**
	*	The width of the board (in cells).
	**/
	private int boardWidth;

	/**
	*	The height of the board (in cells).
	**/
	private int boardHeight;

	/**
	*	The number of mines in this game.
	**/
	private int numberMines;


	/**
	*	The cells making up the minefield.
	**/
	private Cell[][] mineField;
	

	/**
	*	The current game difficulty
	**/
	private int currentGameDifficulty;

	/**
	*	Images used in the display.
	*	Declared public and static, as we want the Cell and LEDPanel classes
	*	to be able to access the images they need.
	**/ 
	public static Image images[];


	private static final int PIXELS_SMILEY_WIDTH = 26;//the smileys are square.
	private static final int PIXELS_SMILEY_HEIGHT = 26;
	private static final int PIXELS_SMILEY_TOP = 14;//the y location of the top.


	//Image indicies (29 images).
	public static final int NUMBER_OF_IMAGES = 30;

	public static final int IMG_ONE = 0;
	public static final int IMG_TWO = 1;
	public static final int IMG_THREE = 2;
	public static final int IMG_FOUR = 3;
	public static final int IMG_FIVE = 4;
	public static final int IMG_SIX = 5;
	public static final int IMG_SEVEN = 6;
	public static final int IMG_EIGHT = 7;
	public static final int IMG_NINE = 8;
	public static final int IMG_SMILEY_SMILE = 9;
	public static final int IMG_SMILEY_O = 10;
	public static final int IMG_SMILEY_DEAD = 11;
	public static final int IMG_SMILEY_GLASSES = 12;
	public static final int IMG_FLAG = 13;
	public static final int IMG_QUESTION_MARK = 14;
	public static final int IMG_MINE = 15;
	public static final int IMG_LED_TOP_ON = 16;
	public static final int IMG_LED_TOP_OFF = 17;
	public static final int IMG_LED_LEFT_ON = 18;
	public static final int IMG_LED_LEFT_OFF = 19;
	public static final int IMG_LED_RIGHT_ON = 20;
	public static final int IMG_LED_RIGHT_OFF = 21;
	public static final int IMG_LED_MID_ON = 22;
	public static final int IMG_LED_MID_OFF = 23;
	public static final int IMG_LED_BOTTOM_ON = 24;
	public static final int IMG_LED_BOTTOM_OFF = 25;
	public static final int IMG_CELL_TOUCHED = 26;
	public static final int IMG_CELL_UNTOUCHED = 27;
	public static final int IMG_ICON = 28;
	public static final int IMG_SMILEY_PRESSED = 29;


	/**
	*	The number of mines yet to be cleared.
	**/	
	private int numMinesLeft;


	/**
	*	Has the 'shift' button been pressed?
	*	(This is used in conjunction with the mouse button on one-buttoned mice)
	**/
	private boolean shiftKeyPressed;



	private boolean LMBPressed;

	private boolean RMBPressed;

	private boolean mouseDragging;


	/**
	*	Has the player stepped on a mine..?
	**/
	private boolean exploded;

	/**
	*	Has the player pressed the smiley button?
	**/
	private boolean LMBSmileyPressed;


	/**
	*	The image to use for the smiley;
	**/
	private Image smileyImage;
	

//	private Cell lastCellVisited;

	private int lastCellX;
	private int lastCellY;

	public Game(Image[] images)
	{

		boardWidth = EASY_WIDTH;
		boardHeight = EASY_HEIGHT;
		numberMines = EASY_NUM_MINES;


		setupMineField();

		printField();




		minesLeftLED = new LEDPanel(3);
		timerLED = new LEDPanel(3);
		minesLeftLED.setValue(numberMines);

		this.images = images;

		addMouseMotionListener(this);
		addMouseListener(this);
		addKeyListener(this);

		LMBPressed = false;
		RMBPressed = false;
		exploded = false;
//		lastCellVisited = null;
		lastCellX = -1;
	
		numberCleared = 0;

		smileyImage = images[IMG_SMILEY_SMILE];

		

	}

	public int getTotalWidth()
	{
	
		return PIXELS_OUTER_NW_SLOPE + PIXELS_NW_SURFACE +
			(PIXELS_INNER_FIELD_SLOPE*2) + PIXELS_SE_SURFACE +
			( boardWidth * Cell.WIDTH);

	}

	public int getTotalHeight()
	{

		return PIXELS_OUTER_NW_SLOPE + (PIXELS_NW_SURFACE*2) +
			PIXELS_SE_SURFACE + (PIXELS_INNER_FIELD_SLOPE*2) +
			(PIXELS_INNER_UPPER_SLOPE*2) + PIXELS_INNER_UPPER_SURFACE_HEIGHT + 
			( boardHeight * Cell.HEIGHT);

	}
	
	public int getBoardHeight(){
		return this.boardHeight;
	}
	
	public int getBoardWidth(){
		return this.boardWidth;
	}

	public boolean checkIfExploded(){
		return this.exploded;
	}
	
	public Cell getCell(Point cellIndex){
		return mineField[cellIndex.x][cellIndex.y];
	}

	/**
	*	FOR DEBUGGING ONLY!
	**/
	private void printField()
	{

		int a,b;
		String curr = " ";
		Cell currentCell;
//		if(true)return;
		for(a=0; a<boardHeight;a ++)
		{
			for(b=0; b<boardWidth;b++)
			{
				
				currentCell = mineField[b][a];
				if(currentCell == null)
				{
					curr = ".";
				}else
				{
					if(currentCell.isMined())
					{
						curr = "X";
					}else if (currentCell.getNumborOfNeighbourMines() > 0)
					{
						curr = ""+currentCell.getNumborOfNeighbourMines();
					}else
					{
						curr = " ";
					}
				}
				System.out.print("["+curr+"]");
			}
			System.out.print("\n");
		}
		System.out.println(" ");

	}


	/**
	*	Setup the minefield with the appropriate number of mines.
	**/
	private void setupMineField()
	{
	
		int currentX;
		int currentY;

		int minesPlaced;

		mineField = new Cell[boardWidth][boardHeight];
		
		//lets create the mined cells first. We'll fill in the other fields
		//later.

		minesPlaced = 0;
		while(minesPlaced < numberMines){

			currentX = (int)(Math.random() * boardWidth);
			currentY = (int)(Math.random() * boardHeight);

//			System.out.println("["+currentX+"]["+currentY+"].");
			if(mineField[currentX][currentY] == null){
//				System.out.println("["+currentX+"]["+currentY+"].");
				
				//there is no cell at this location, so lets create a mined 
				//cell here.
				mineField[currentX][currentY] = new Cell(true);
				minesPlaced++;

			}

		}

//		printField();
		System.out.println("	=	=	=	=");
		//now, lets fill in the rest of the cells to be empty.
		//re-use the currentX and currentY variables.
		for(currentY = 0; currentY < boardHeight; currentY++)
		{

			for(currentX = 0; currentX < boardWidth; currentX++)
			{

				if(mineField[currentX][currentY] == null)
				{
					mineField[currentX][currentY] = new Cell(false);
					//lets calculate the number of mines connected to this cell
					//while we're in this loop.
//					System.out.println("["+currentX+"]["+currentY+"]");
					mineField[currentX][currentY].setNumberOfNeighbourMines(
								calculateConnectedMines(currentX,currentY));

				}

			}

		}


	}

	/**
	*	A method to calculate the number of mines that are connectd to the 
	*	current cell.
	**/
	private int calculateConnectedMines(int x,int y)
	{

		int ans = 0;
		int new_x;//used for loops.
		int new_y;

		Cell currentCell;
		
		new_x = x - 1;
		new_y = y - 1;

		for(new_y = y-1;new_y < y + 2; new_y++)
		{

			//check for a valid range.
			if(new_y >= 0 && new_y < boardHeight)
			{
				for(new_x = x-1; new_x < x + 2;new_x ++)
				{
					
					if(new_x >= 0 && new_x < boardWidth)
					{
//						System.out.println("("+new_x+","+new_y+")");
						currentCell = mineField[new_x][new_y];
						if(currentCell != null && currentCell.isMined())
						{
							ans++;
						}
					}

//					new_x++;//move along the row.
				}

			}
//			new_x = x - 1;//reset the row.
//			new_y++;//move along the column (down one)

		}

		return ans;

	}


	
	/**
	*	A method to change the game difficulty setting.
	*	As in the windows version (windows 2000), the width of the board cannot
	*	exceed 30, and the height cannot exceed 23.
	*	A check is performed to see if the number of mines does not exceed 
	*	(width * height) = (width + height - 1).
	**/

	public void setDifficulty(int setting, int width, int height,
									int numMines)
		throws IllegalArgumentException
	{
	
		switch (setting)
		{
		case GAME_DIFFICULTY_BEGINNER:

			boardWidth = EASY_WIDTH;
			boardHeight = EASY_HEIGHT;
			numberMines = EASY_NUM_MINES;
			break;

		case GAME_DIFFICULTY_INTERMEDIATE:

			boardWidth = INTERMEDIATE_WIDTH;
			boardHeight = INTERMEDIATE_HEIGHT;
			numberMines = INTERMEDIATE_NUM_MINES;
			break;

		case GAME_DIFFICULTY_EXPERT:

			boardWidth = EXPERT_WIDTH;
			boardHeight = EXPERT_HEIGHT;
			numberMines = EXPERT_NUM_MINES;
			break;

		case GAME_DIFFICULTY_CUSTOM:

			if(width <= 0)
			{
				throw new IllegalArgumentException("Field width must be a "
												+"positive integer.");
			}

			if( height  <= 0)
			{
				throw new IllegalArgumentException("Field height must be a "
												+"positive integer.");
			}

			//constraints are checked in CustomFieldDialog.
			boardWidth = width;
			boardHeight = height;
			numberMines = numMines;

			break;

		default:
			throw new IllegalArgumentException("Game difficulty can only be "
						+"Easy, Intermediate, Expert or Custom...");
		}

		currentGameDifficulty = setting;

		numberCleared = 0;
		graphicsBuffer = null;
		setupMineField();
		exploded = false;
		timerLED.setValue(0);
		lastCellX = -1;
		minesLeftLED.setValue(numberMines);
//		printField();
		setSize(getTotalWidth(),getTotalHeight());

		smileyImage = images[IMG_SMILEY_SMILE];

		repaint();

	}


	public void repaint()
	{

//		System.out.println("repaint");
		paint(this.getGraphics());

	}

	public void update(Graphics g)
	{

//		System.out.println("update");
		paint(g);

	}

	/**
	*	Display the board to a certain graphics context.
	**/
	public void paint(Graphics g)
	{

		if(graphicsBuffer == null)
		{

			System.out.println("Re-creating buffer with dimenstions:"+
						getTotalWidth()+","+getTotalHeight());
			imageBuffer = createImage(getTotalWidth(),
									getTotalHeight());
			graphicsBuffer = imageBuffer.getGraphics();

		}
		paintOuterBoarder(graphicsBuffer);
		displayUpperSection(graphicsBuffer);
		displayMineField(graphicsBuffer);
		g.drawImage(imageBuffer,0,0,this);
//		g.fillRect(0,0,boardWidth * Cell.WIDTH,boardHeight * Cell.HEIGHT);
 
	}

	/**
	*	Paint the outer boarder of the board.
	*	Left:	3 pixels white
	*			6 pixels light gray
	*	Top:	3 pixels white
	*			6 pixels light gray
	*
	*	The upper portion is 92 pixels high.
	*	The total height of the board is 115 * (boardHeigh * Cell.HEIGHT).
	**/
	private void paintOuterBoarder(Graphics g)
	{

		g.setColor(INNER_SHADING);
		g.fillRect(0,0,getTotalWidth(),getTotalHeight());
		g.setColor(OUTER_SHADING);
		int i; // used in loops
		for(i=0;i<PIXELS_OUTER_NW_SLOPE + PIXELS_NW_SURFACE;i++)
		{
			if(i == PIXELS_OUTER_NW_SLOPE)
			{

				g.setColor(SURFACE_SHADING);

			}

			//left portion
			g.drawLine(i,i,i,getTotalHeight());
			//upper portion
			g.drawLine(i,i,getTotalWidth(),i);

		}
		//do the south-east surface area now.
		for (i = 0; i < PIXELS_SE_SURFACE ; i++)
		{

			g.drawLine(PIXELS_OUTER_NW_SLOPE,
						getTotalHeight() - PIXELS_SE_SURFACE + i,
						getTotalWidth(),
						getTotalHeight() - PIXELS_SE_SURFACE + i) ;

			g.drawLine(getTotalWidth() - PIXELS_SE_SURFACE + i,
						PIXELS_OUTER_NW_SLOPE,
						getTotalWidth() - PIXELS_SE_SURFACE + i,
						getTotalHeight());

		}



//		g.drawLine(1,0,1,OUTER_BOARD_HEIGHT +( boardHeight * Cell.HEIGHT));
	
//		g.fill3DRect(0,0,20,20,true);

	}

	/**
	*	Display the upper beveld section
	**/
	private void displayUpperSection(Graphics g)
	{

		//inner shading is done already, so lets just do the outer shading
		//(i.e. the S-E portion), as well as the bottom part of the
		//upper surface.
		g.setColor(OUTER_SHADING);
		int o_x,o_y,d_x,d_y;
		for(int i=0; i< PIXELS_INNER_UPPER_SLOPE;i++)
		{
			o_x = getTotalWidth() - PIXELS_SE_SURFACE -
					PIXELS_INNER_UPPER_SLOPE + i;
			d_x = o_x;

			o_y = PIXELS_OUTER_NW_SLOPE + PIXELS_INNER_UPPER_SLOPE+
					PIXELS_NW_SURFACE - i;
			d_y = o_y + PIXELS_INNER_UPPER_SURFACE_HEIGHT + (i*2);

			g.drawLine(o_x,o_y,d_x,d_y);	

			//now lets do the lower surface.
			o_x = PIXELS_OUTER_NW_SLOPE + PIXELS_NW_SURFACE +
					PIXELS_INNER_UPPER_SLOPE - i;
			d_x = getTotalWidth() - PIXELS_SE_SURFACE -
					PIXELS_INNER_UPPER_SLOPE + i;

			o_y = d_y;
			g.drawLine(o_x,o_y,d_x,d_y);	

		}
		//and the beveld surface.
		o_x = PIXELS_OUTER_NW_SLOPE + PIXELS_NW_SURFACE +
						PIXELS_INNER_UPPER_SLOPE;

		o_y = o_x;
		d_x = getTotalWidth() - PIXELS_SE_SURFACE - PIXELS_INNER_UPPER_SLOPE - o_x;//width
		d_y = PIXELS_INNER_UPPER_SURFACE_HEIGHT;

		g.setColor(SURFACE_SHADING);
		g.fillRect(o_x,o_y,d_x,d_y);

		//and last of all, the 'upper' middle boundry.
		o_x = PIXELS_OUTER_NW_SLOPE;
		d_x = getTotalWidth() - o_x;
		o_y = o_y + d_y + 2;
		d_y = PIXELS_NW_SURFACE;

		g.fillRect(o_x,o_y,d_x,d_y);

		//lets now do the smiley
		o_x = (getTotalWidth()/2) - (PIXELS_SMILEY_WIDTH/2) + 1;
		o_y = PIXELS_SMILEY_TOP;

	//	if(LMBPressed)
	//	{
			g.drawImage(smileyImage,o_x,o_y,this);
	//	}else
	//	{
	//		g.drawImage(images[IMG_SMILEY_SMILE],o_x,o_y,this);
	//	}

		//and the LED displays
		o_x =  PIXELS_OUTER_NW_SLOPE + PIXELS_NW_SURFACE +
						PIXELS_UPPER_BESIDE_LED + PIXELS_INNER_UPPER_SLOPE;
		o_y = PIXELS_OUTER_NW_SLOPE + PIXELS_NW_SURFACE +
						PIXELS_UPPER_ABOVE_LED + PIXELS_INNER_UPPER_SLOPE;


		minesLeftLED.display(g,this,o_x,o_y);

		o_x = getTotalWidth() - timerLED.getDisplayWidth() - 
					PIXELS_NW_SURFACE - PIXELS_INNER_UPPER_SLOPE -
					PIXELS_UPPER_BESIDE_LED;

		timerLED.display(g,this,o_x,o_y);


	}
	/**
	*	Display the minefield. This includes the 3D shading at the edges.
	**/
	private void displayMineField(Graphics g)
	{


		//lets do the 3D shading at the S-E edges first.
		//note that the N-W slope has already been done.
		int o_x,o_y,d_x,d_y;
		g.setColor(OUTER_SHADING);
		for(int i=0;i<PIXELS_INNER_FIELD_SLOPE;i++)
		{

			o_x = getTotalWidth() - PIXELS_SE_SURFACE -
					PIXELS_INNER_FIELD_SLOPE + i;
			d_x = o_x;

			o_y = getTotalHeight() - (boardHeight * Cell.HEIGHT) - 
					PIXELS_INNER_FIELD_SLOPE - PIXELS_SE_SURFACE - i;

			d_y = o_y + (boardHeight * Cell.HEIGHT) + (i*2);

			g.drawLine(o_x,o_y,d_x,d_y);	

			//now lets do the lower surface.
			o_x = PIXELS_OUTER_NW_SLOPE + PIXELS_NW_SURFACE +
					PIXELS_INNER_FIELD_SLOPE - i;
			d_x = getTotalWidth() - PIXELS_SE_SURFACE -
					PIXELS_INNER_FIELD_SLOPE + i;

			o_y = d_y;
			g.drawLine(o_x,o_y,d_x,d_y);	

		}

		g.setColor(SURFACE_SHADING);
		for(int i = 0; i < boardHeight; i++)
		{
			for(int ii = 0; ii < boardWidth; ii++)
			{

//				g.fill3DRect(PIXELS_FIELD_LEFTMOST + (ii * Cell.WIDTH),
//					PIXELS_FIELD_TOPMOST + (i * Cell.HEIGHT),
//					Cell.WIDTH,Cell.HEIGHT, true);
				mineField[ii][i].display(g,
						PIXELS_FIELD_LEFTMOST + (ii * Cell.WIDTH),
						PIXELS_FIELD_TOPMOST + (i * Cell.HEIGHT));
//				g.drawImage(images[IMG_CELL_UNTOUCHED],
//						PIXELS_FIELD_LEFTMOST + (ii * Cell.WIDTH),
//						PIXELS_FIELD_TOPMOST + (i * Cell.HEIGHT),
//						this);

			}

		}


	}

	
	/**
	*
	**/
	public void displayTimerPanel()
	{

		int origin_x,origin_y,width,height;//the bounding rectangle of the panel.

		origin_x = getTotalWidth() - timerLED.getDisplayWidth() - 
					PIXELS_NW_SURFACE - PIXELS_INNER_UPPER_SLOPE -
										PIXELS_UPPER_BESIDE_LED;

		origin_y  = PIXELS_OUTER_NW_SLOPE + PIXELS_NW_SURFACE +
						PIXELS_UPPER_ABOVE_LED + PIXELS_INNER_UPPER_SLOPE;

		width = timerLED.getDisplayWidth();
		height = timerLED.getDisplayHeight();

		repaint(origin_x,origin_y,width,height);

	}





	private void repaintSmiley()
	{

		int o_x,o_y;

		o_x = (getTotalWidth()/2) - (PIXELS_SMILEY_WIDTH/2) + 1;
		o_y = PIXELS_SMILEY_TOP;

		repaint(o_x,o_y,PIXELS_SMILEY_WIDTH,PIXELS_SMILEY_HEIGHT);
	 


	}


	/**	
	*	Is the specified location within the minefield?
	**/ 

	private boolean isInMineField(int x,int y)
	{

		int top_x,top_y,width,height;

		width = boardWidth * Cell.WIDTH;
		height = boardHeight * Cell.HEIGHT;

		top_x = PIXELS_OUTER_NW_SLOPE + PIXELS_NW_SURFACE + PIXELS_INNER_FIELD_SLOPE;
		top_y = getTotalHeight() - PIXELS_SE_SURFACE - PIXELS_INNER_FIELD_SLOPE - height;


		return (	(x >= top_x && x < top_x + width) &&
					(y >= top_y && y < top_y + height));
	}

	private boolean isInSmiley(int x,int y)

	{

		int top_x,top_y;
		top_x = (getTotalWidth()/2) - (PIXELS_SMILEY_WIDTH/2) + 1;
		top_y = PIXELS_SMILEY_TOP;

		return (	(x >= top_x && x <= top_x + PIXELS_SMILEY_WIDTH)&&
					(y > top_y && y <= top_y + PIXELS_SMILEY_HEIGHT));

	}


	public void newGame()
	{

		stopGame();
		setDifficulty(currentGameDifficulty,boardWidth,boardHeight,numberMines);

	}

	//
	// Mouse Listener interface implementations
	//

	public void mouseClicked(MouseEvent e) 
	{
	}
	
	public void mouseEntered(MouseEvent e) 
	{
	}
	

	public void mouseExited(MouseEvent e) 
	{
	}
	
	 public void mousePressed(MouseEvent e) 
	{
		
		Point cellIndex;

		if(exploded && isInMineField(e.getX(),e.getY()))
		{
			return;
		}

		if(smileyImage.equals(images[IMG_SMILEY_GLASSES]) &&
				!isInSmiley(e.getX(),e.getY()))
		{
			return;
		}
		if(e.getButton() == MouseEvent.BUTTON1 && !shiftKeyPressed)
		{

			if(isInSmiley(e.getX(),e.getY()))
			{
				LMBSmileyPressed = true;
				smileyImage = images[IMG_SMILEY_PRESSED];
				repaintSmiley();

			}else
			{
				LMBPressed = true;
				if(exploded)
				{
					smileyImage = images[IMG_SMILEY_DEAD];
				}else
				{
					smileyImage = images[IMG_SMILEY_O];
				}

				repaintSmiley();
				cellIndex = getCellIndex(e.getX(),e.getY());
				if(cellIndex != null)
				{
					lastCellX = (int)cellIndex.getX();
					lastCellY = (int)cellIndex.getY();

//					lastCellVisited = mineField[(int)cellIndex.getX()][(int)cellIndex.getY()];
					if(mineField[lastCellX][lastCellY].getState() != Cell.STATE_FLAGGED)
					{
						mineField[lastCellX][lastCellY].press();
					}
					repaint();

				}
			}



		}else
		{

			if(!isInMineField(e.getX(),e.getY()))
			{
				return;
			}

			cellIndex = getCellIndex(e.getX(),e.getY());
			lastCellX = (int)cellIndex.getX();
			lastCellY = (int)cellIndex.getY();

//			lastCellVisited = mineField[(int)cellIndex.getX()][(int)cellIndex.getY()];
			mineField[lastCellX][lastCellY].cycleRMBState();
			if(mineField[lastCellX][lastCellY].getState() == Cell.STATE_FLAGGED)
			{
				minesLeftLED.decrement();
			}else if(mineField[lastCellX][lastCellY].getState() == Cell.STATE_MARKED)
			{
				minesLeftLED.increment();
			}


			//System.out.println("Num cleared:"+numberCleared+"."+(boardWidth * boardHeight - numberMines));


			if(numberCleared == boardWidth * boardHeight - numberMines)
			{
				System.out.println("Game over. :)");
				minesLeftLED.setValue(0);
				flagRemainingCells();
				stopGame();
				smileyImage = images[IMG_SMILEY_GLASSES];
				repaint();
				return;
			}


			repaint();

		}

		
	}
	
	 public void mouseReleased(MouseEvent e) 
	{

		if(smileyImage.equals(images[IMG_SMILEY_GLASSES]) &&
				!isInSmiley(e.getX(),e.getY()))
		{
			return;
		}
		
		if(e.getButton() == MouseEvent.BUTTON1 && !shiftKeyPressed && !RMBPressed)
		{

			if(LMBSmileyPressed)
			{
				if(isInSmiley(e.getX(),e.getY()))
				{
					newGame();
				}

			}
			if(lastCellX != -1)
			{
				if(mineField[lastCellX][lastCellY].getState() == Cell.STATE_PRESSED)
				{

					expandSelectedCell(lastCellX,lastCellY);


					if(numberCleared == boardWidth * boardHeight - numberMines )
					{
						System.out.println("Game over. :)");
						stopGame();
						minesLeftLED.setValue(0);
						flagRemainingCells();
						smileyImage = images[IMG_SMILEY_GLASSES];
						repaint();
						return;
					}


					if(mineField[lastCellX][lastCellY].isMined())
					{
						  if(this.numberCleared == 0 || this.numberCleared == 1 || this.numberCleared == 2 || this.numberCleared == 3) //first, second or third round
						  {	
							  System.out.println("Bomb in first round. Restarting.");
							  try{
								  Main.restartApplication(null);
							  }catch(Exception ex){
							  
							  }
						  }
						  else{ 					 
						     //game over!
							 stepOnMine();
							 repaint();
//							 lastCellVisited = null;
							 lastCellX = -1;
							 return;
						 }
					}else if(mineField[lastCellX][lastCellY].getNumborOfNeighbourMines() == 0)
					{
						//expand the selection.
//						mineField[lastCellX][lastCellY].clean();
					}

					lastCellX = -1;
				}
			}
			LMBPressed = false;
			LMBSmileyPressed = false;

		}else
		{
			if(e.getButton() == MouseEvent.BUTTON1)
			{
				//both buttons were pressed.
				if(lastCellX != -1)
				{
					mineField[lastCellX][lastCellY].clean();
					lastCellX = -1;
				}
			}
			RMBPressed = false;
		}

		if(!gameRunning && isInMineField(e.getX(),e.getY()) && !exploded)
		{
//			System.out
			gameRunning = true;
			Thread t = new Thread(this);
			t.start();

		}

		if(!exploded)
		{
			smileyImage = images[IMG_SMILEY_SMILE];
		}
		repaint();

	}
  
	//
	// MouseMotionListener interface implementations
	//

	public void mouseDragged(MouseEvent e) 
	{

		repaintSmiley();
		mouseDragging = true;
		Point cellIndex;

		if(exploded && isInMineField(e.getX(),e.getY()))
		{
			return;
		}

		if(smileyImage.equals(images[IMG_SMILEY_GLASSES]) &&
				!isInSmiley(e.getX(),e.getY()))
		{
			return;
		}
		
		if(LMBSmileyPressed)
		{
 
			if(isInSmiley(e.getX(),e.getY()))
			{
				smileyImage = images[IMG_SMILEY_PRESSED];
			}else if(exploded)
			{
				smileyImage = images[IMG_SMILEY_DEAD];
			}else
			{
				if(!gameRunning && lastCellX != -1)
				{
					smileyImage = images[IMG_SMILEY_GLASSES];
				}else
				{
					smileyImage = images[IMG_SMILEY_SMILE];
				}
			}

		}else
		{
		
			if(exploded)
			{
				smileyImage = images[IMG_SMILEY_DEAD];
			}else if(LMBPressed)
			{
				smileyImage = images[IMG_SMILEY_O];
			}

		}

		if(LMBPressed)
		{
			cellIndex = getCellIndex(e.getX(),e.getY());
			if(lastCellX != -1)
			{
				mineField[lastCellX][lastCellY].clean();
			}
			if(cellIndex != null)
			{
				lastCellX = (int)cellIndex.getX();
				lastCellY = (int)cellIndex.getY();


				if(mineField[lastCellX][lastCellY].getState() != Cell.STATE_FLAGGED)
				{
					mineField[lastCellX][lastCellY].press();
				}
			}

			repaint();



		}

	}

	public void mouseMoved(MouseEvent e) 
	{

		Point p = getCellIndex(e.getX(),e.getY());

		if(p != null)
		{
//			System.out.println("Cell: ["+p.getX()+","+p.getY()+"]");
		}

	}


	//
	//	KeyListener interface method implementations
	//
	public void keyPressed(KeyEvent e) 
	{

		//only used for the 'shift' key at the moment.
		if(e.getKeyCode() == KeyEvent.VK_SHIFT)
		{

			System.out.println("Shift pressed.");
			shiftKeyPressed = true;
			if(LMBPressed)
			{
				RMBPressed = true;
			}

		}

	}
	
	
	public  void keyReleased(KeyEvent e) 
	{
		//only used for the 'shift' key at the moment.
		if(e.getKeyCode() == KeyEvent.VK_SHIFT)
		{

			System.out.println("Shift released.");
			shiftKeyPressed = false;

			if(LMBPressed)
			{
				RMBPressed = false;
			}

		}

	}
	
	public  void keyTyped(KeyEvent e) 
	{
	}
	
	//
	//
	//
	public void run()
	{

		Thread t = Thread.currentThread();
		t.setPriority(Thread.MIN_PRIORITY);
		while(gameRunning)
		{

			try
			{
				t.sleep(1000);
				if(gameRunning)
				{
					timerLED.increment();
					displayTimerPanel();
				}

			}catch(InterruptedException ioe)
			{
			}

		}

	}



	/**
	*	Stop the current game.
	**/
	public void stopGame()
	{

		gameRunning = false;

	}



	/**
	*	A function to get the row and column of a cell (index in mineField array),
	*	given a screen co-ordinte.
	*	Returns null if the location isn't in the field.
	**/
	private Point getCellIndex(int x,int y)
	{

		if(!isInMineField(x,y))
		{
			return null;
		}

		x -= PIXELS_OUTER_NW_SLOPE + PIXELS_NW_SURFACE + PIXELS_INNER_FIELD_SLOPE;
		y -= getTotalHeight() - PIXELS_SE_SURFACE - PIXELS_INNER_FIELD_SLOPE - 
				(boardHeight * Cell.HEIGHT);

		return new Point( x / Cell.WIDTH, y / Cell.HEIGHT);

	}



	/**
	*	Kabloey!
	**/

	private void stepOnMine()
	{

		System.out.println("Boom.");
		exploded = true;
		displayMines();
		smileyImage = images[IMG_SMILEY_DEAD];
		stopGame();

	}

	/**
	*	Diaplay all mines.
	**/

	private void displayMines()
	{


		for(int i=0; i< boardWidth; i++){
			for(int ii=0; ii< boardHeight; ii++){

					mineField[i][ii].displayMine();

			}
		}

		stopGame();

	}

		

	/**
	*	Flag any remaining cells... (game completed)
	**/

	private void flagRemainingCells()
	{


		for(int i=0; i< boardWidth; i++){
			for(int ii=0; ii< boardHeight; ii++){

				if(mineField[i][ii].getState() == Cell.STATE_CLEAN)
				{
					mineField[i][ii].flag();
				}

			}
		}

		stopGame();

	}

		


	private void expandSelectedCell(int selectedX,int selectedY)
	{

		int cell_index_x,cell_index_y;//should these be attributes..?

		int loop_x,loop_y;

		cell_index_x = 0;
		cell_index_y = 0;


//		System.out.println("Pheras");
		if((mineField[selectedX][selectedY].getState() != Cell.STATE_CLEAN &&
			mineField[selectedX][selectedY].getState() != Cell.STATE_PRESSED) ||
			mineField[selectedX][selectedY].isMined())
		{
	
			return;
	
		}

		mineField[selectedX][selectedY].visit();
		numberCleared++;
//		System.out.println("Cleared:"+numberCleared+":"+(boardWidth * boardHeight - numberMines));

		if(mineField[selectedX][selectedY].getNumborOfNeighbourMines() != 0)
		{
			return;
		}

		//find the location in the Cell array of the currently selected cell.
		for(loop_x = 0; loop_x < 3; loop_x ++)
		{

			cell_index_x = selectedX - 1 + loop_x;

			for(loop_y = 0; loop_y < 3; loop_y++)
			{
				
				cell_index_y = selectedY -1 + loop_y;

//				System.out.println("[]Cell index x:"+cell_index_x+",y:"+cell_index_y);

				if(cell_index_x >= 0 && cell_index_x < boardWidth &&
					cell_index_y >= 0 && cell_index_y < boardHeight)
				{

					expandSelectedCell(cell_index_x,cell_index_y);					

				}

			}

		}

	}

}
